/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.yonsei.iwb.sdt.algorithm;

import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.Property;
import com.hp.hpl.jena.rdf.model.Resource;
import java.util.Random;
import java.util.Vector;
import org.yonsei.iwb.sdt.datastructure.DecomposeCandidate;
import org.yonsei.iwb.sdt.datastructure.DecomposeCandidateSet;
import org.yonsei.iwb.sdt.datastructure.tree.DEdge;
import org.yonsei.iwb.sdt.datastructure.tree.DNode;
import org.yonsei.iwb.sdt.datastructure.tree.NodeType;
import org.yonsei.iwb.sdt.datastructure.TargetInstanceSet;
import org.yonsei.iwb.sdt.datastructure.TargetInstanceValueInformation;
import org.yonsei.iwb.sdt.ontology.ClassModel;
import org.yonsei.iwb.sdt.ontology.PropertyArc;
import org.yonsei.iwb.sdt.refinement.ConceptConstructorRefinement;
import org.yonsei.iwb.sdt.refinement.Refinement;
import org.yonsei.iwb.sdt.refinement.RefinementType;
import org.yonsei.iwb.sdt.refinement.generator.QualifiedConjunctionRefinementMaker;
import org.yonsei.iwb.sdt.util.SDTLog;

/**
 *
 * @author DK
 */
public class TreeDecomposer {

//    private SearchDecomposeCandidateSet sdcs;
    private Model model;
    private ClassModel classmodel;
    private int learningOption;
    private TargetInstanceSet tis;
    private double ENTROPY_THRESHOLD;
    private int maxcandidate = 0;
    private int loop = 0;
//    private ResourceWeights rws;

    public TreeDecomposer(Model model, ClassModel classmodel, int opt, TargetInstanceSet tis, double threshold) {
        this.model = model;
        this.classmodel = classmodel;
        this.learningOption = opt;
        this.tis = tis;
//        this.rws = new ResourceWeights(model);
        this.ENTROPY_THRESHOLD = threshold;
    }
    
    public void setinit(){
        this.maxcandidate = 0;
    }

    public void decomposeTreeNode(DNode node, DecomposeCandidateSet dcs) {
        if (dcs.isEmpty()) {
            SDTLog.log(SDTLog.BASIC, "# CandidateSet is not Exist\n");
            return;
        }
        if(maxcandidate < dcs.size()){
//            System.out.println("# Number of candidates : " + dcs.size());
            maxcandidate = dcs.size();
        }        
//        SDTLog.log(SDTLog.DETAIL, "# Number of candidates : " + dcs.size());
        dcs.calculateInformationGain(node, tis);
        DecomposeCandidate bestdc = this.findBestDecomposeCandidate(dcs);
//        System.out.println("best : " + bestdc.explanation());
//        System.out.println("------------------- " + this.loop + " loop -------------------");
//        dcs.printDecomposeCandidateSet();
        this.loop++;
//        System.out.println("--------------------------------------------------------------");

        Vector<DEdge> edgeset = bestdc.generateEdge(node);
        dcs.remove(bestdc);

        for (int i = 0; i < edgeset.size(); i++) {
            DEdge edge = edgeset.get(i);
            DNode childnode = edge.getChildNode();
            Refinement bref = edge.getRefinement();
            boolean positiveEdge = bref.getBranchingType();
            if (this.checkLeafNode(childnode)) {                
                childnode.NODE_TYPE = NodeType.LEAFNODE;
            } else {                
                childnode.NODE_TYPE = NodeType.BRANCHNODE;
                int refinementType = bestdc.getRefinementType();
                DecomposeCandidateSet newdcs = this.updateExistRefinements(bref, dcs);
                if (positiveEdge) {
                    if (refinementType == RefinementType.TOPCONCEPTCONSTRUCTOR) {
                        newdcs.addAll(this.nextDecomposeCandidateSetSearcher(bref, dcs));
                    } else {
                        if (refinementType < RefinementType.IS_EXPANDED) {
//                        System.out.println("1");
                            if (refinementType == RefinementType.QUALIFICATION) {
//                            System.out.println("2");
                                newdcs.addAll(this.nextDecomposeCandidateSetSearcher(bestdc.getPropertyArc(), bref, dcs, true));
                            } else {
//                            System.out.println("3");
                                newdcs.addAll(this.nextDecomposeCandidateSetSearcher(bestdc.getPropertyArc(), bref, dcs, false));
                            }
                        }
                    }
                }

                if (!newdcs.isEmpty()) {
                    decomposeTreeNode(childnode, newdcs);
                } else {
                    SDTLog.log(SDTLog.DETAIL, "Further candidate set is not exist\n");
                    childnode.NODE_TYPE = NodeType.LEAFNODE;
                }

            }
        }
    }

    private boolean checkLeafNode(DNode node) {

        TargetInstanceValueInformation tivi = node.getTargetInstanceValueInformation();
//        int valueSize = SDTLearner.TARGETVALUESIZE;

        if (tivi.getClassValueSize() == 1) {
            return true;
        } else if (node.getInstanceSetSize() < 10) {
            return true;
        } else if (node.getEntropy() <= this.ENTROPY_THRESHOLD) {
            return true;
        }

        return false;
    }

    /**
     * 왜 기존의 refinement 가 한개 이상이어야 하는지..20130712 확인못함 아직
     *
     * @param parentds
     * @param ref
     * @param olddcs
     * @return
     */
    private DecomposeCandidateSet updateExistRefinements(Refinement ref, DecomposeCandidateSet olddcs) {
        DecomposeCandidateSet newdcs = new DecomposeCandidateSet();
        newdcs.setPropertySet(olddcs.getPropertySet());
        Vector<Resource> instanceSet = ref.getInstanceSet();
        for (int i = 0; i < olddcs.size(); i++) {
            DecomposeCandidate olddc = olddcs.get(i);
            Vector<Refinement> vset = this.updateRefinements(instanceSet, olddc.getRefinementSet());
//            if(vset.size() > 1){
            DecomposeCandidate newdc = new DecomposeCandidate(vset, olddc.getPropertyArc(), olddc.getRefinementType());
            newdcs.add(newdc);
//            }
        }
        return newdcs;
    }

    private Vector<Refinement> updateRefinements(Vector<Resource> instanceSet, Vector<Refinement> refset) {
        Vector<Refinement> newrefSet = new Vector<Refinement>();

        for (int i = 0; i < refset.size(); i++) {
            Refinement ref = refset.get(i);

            Refinement newref = ref.generateUpdatedRefinement(instanceSet);
            if (!newref.getInstanceSet().isEmpty()) {
                newrefSet.add(newref);
            }
        }
        return newrefSet;
    }

    /**
     * Top concept 으로부터 탐색
     *
     * @param parentds
     * @param ref
     * @param olddcs
     * @return
     */
    private DecomposeCandidateSet nextDecomposeCandidateSetSearcher(Refinement ref, DecomposeCandidateSet olddcs) {
        DecomposeCandidateSet newdcs = new DecomposeCandidateSet();
        newdcs.setTargetclass(((ConceptConstructorRefinement) ref).getConditionResource());
        DCSSearcher sdcs = new DCSSearcher(this.model, this.classmodel, this.learningOption);
        sdcs.searchDecomposeCandidateSet(newdcs, ref.getInstanceSet(), olddcs.getPropertySet());
        return newdcs;
    }

    private DecomposeCandidateSet nextDecomposeCandidateSetSearcher(PropertyArc expandArc, Refinement ref, DecomposeCandidateSet olddcs, boolean isQualfication) {
        Vector<Resource> instanceSet = ref.getInstanceSet();
        DecomposeCandidateSet expandeddcs = new DecomposeCandidateSet();

        DCSSearcher sdcs = new DCSSearcher(this.model, this.classmodel, this.learningOption);
        Vector<PropertyArc> propertySet = new Vector(olddcs.getPropertySet());
        propertySet.add(expandArc);
        Vector<Resource> rangeset = sdcs.getConnectedClasses(expandArc);
        for (int i = 0; i < rangeset.size(); i++) {
            Resource range = rangeset.get(i);
            expandeddcs.setTargetclass(range);
            sdcs.searchDecomposeCandidateSet(expandeddcs, instanceSet, propertySet);
        }

        
        //************************* qualified conjunction refinement...
        if (false) {
            QualifiedConjunctionRefinementMaker qcrm = new QualifiedConjunctionRefinementMaker(this.model, instanceSet);
            DecomposeCandidateSet qcrmDCS = qcrm.refinementGenerator(ref, expandeddcs);
            expandeddcs.addAll(qcrmDCS);
        }
        return expandeddcs;
    }

    private DecomposeCandidate findBestDecomposeCandidate(DecomposeCandidateSet dcs) {
        DecomposeCandidate firstDC = dcs.firstElement();
        
        double presentBestValue = firstDC.getInformation_Gain();
//        System.out.println("# 첫번째 refinement : \n" + firstDC.explanation());

        int bestResult = 0;
        for (int present = 1; present < dcs.size(); present++) {
//            System.out.println("------------------------***********" + present + "***********---------------------------------");
            DecomposeCandidate nextDC = dcs.get(present);            
//            System.out.println("# next refinements : \n" + nextDC.explanation());
            double nextGainValue = nextDC.getInformation_Gain();

            if (presentBestValue < nextGainValue) {
//                System.out.println("    %% next 가 더 gain이 크다.");
                presentBestValue = nextGainValue;
                bestResult = present;

            } else if (presentBestValue == nextGainValue) {
//                System.out.println("    %% next와 present의 gain이 같다.");
                DecomposeCandidate presentDC = dcs.get(bestResult);
//                SDTLog.log(SDTLog.DETAIL, "# present refinements : \n" + presentDC.explanation() + " \n");
                // 현재 결과 값과 새로운 결과값의 Refinement의 가중치를 비교하여 더 좋은 것으로 선택
                // 확장이 가능한 값이 최대한 선택 되도록 하자 - Cardinalty or Qualification 우선
                if (nextDC.getRefinementType() <= RefinementType.IS_EXPANDED) {
                    if (presentDC.getRefinementType() <= RefinementType.IS_EXPANDED) {
//                        System.out.println("    %% next와 present가 모두 expanded");
                        // 둘다 확장 가능이므로 추가 비교                        
                        //비교방식 1. 더 많이 확장된 리파인먼트를 선택하고 탐색의 범위를 넓히자
                        if (presentDC.getExpansionDepth() < nextDC.getExpansionDepth()) {
//                            System.out.println("        %% next의 깊이가 더 깊다.");
                            presentBestValue = nextGainValue;
                            bestResult = present;
                        } else if (presentDC.getExpansionDepth() == nextDC.getExpansionDepth()) {
                            Random prob = new Random();
                            if (prob.nextDouble() > 0.5) {
                                presentBestValue = nextGainValue;
                                bestResult = present;
                            }
//                            System.out.println("        %% next와 present의 깊이가 같다.");
                            //비교방식 2. 확장의 깊이가 같을경우 프로퍼티의 가중치 비교를 통해 결정하자. 이것도 같으면 아무거나..                            

//                            double presentweight = this.rws.get(presentDC.getProperty());
//                            System.out.println("            %% present weight : " + presentweight);
//                             
//                            double nextweight = this.rws.get(nextDC.getProperty());
//                            System.out.println("            %% next weight : " + nextweight);
//                            if(presentweight < nextweight){
//                                SDTLog.log(SDTLog.DETAIL, "             %% next weight가 더 크다.\n");
                            // 현재는 가중치가 큰게 더 좋은것으로 세팅해 놓음
//                                presentBestValue = nextGainValue;
//                                bestResult = present;                                
//                            }
                        }
                    } else {
                        presentBestValue = nextGainValue;
                        bestResult = present;
                    }
                } else {
                    if (presentDC.getRefinementType() >= RefinementType.IS_EXPANDED) {
//                        System.out.println("???");
                        // 둘다 확장 불가이기 때문에 추가적인 판단 방법이 필요함 (클래스 간의 중요도 비교 등...)                     
                        presentBestValue = nextGainValue;
                        bestResult = present;
                    }
                }
            }
        }

        firstDC = dcs.get(bestResult);
        return firstDC;
    }

    private Vector<Property> updatePropertyHistory(Property p, Vector<Property> old) {
        Vector<Property> propertySet = new Vector<Property>();
        for (int i = 0; i < old.size(); i++) {
            propertySet.add(old.get(i));
        }
        propertySet.add(p);
        return propertySet;
    }
}
